
/*

Author: Michele Petrazzo <michele.petrazzo@gmail.com>
Date: April 2010

Description:
  This plugin let's you to interact with pppd via external program(s).

Todo:
  Since only pap hooks are done, please contribute with chap ones.

Security holes:
  A simple `ps ax | grep value_of_pap_auth_hook` executed in the right instant
  show the username and the password that pppd is trying to authenticate.
  This cannot be a big problem since on the server where ppppd is running it's, normally,
  not designed for guest users other than admins.

Disclaimer:
  I'm not a C developer, so don't insult me for the quality of this software. I wrote
  it only because there wasn't a "external plugin" for pppd already done!
  
  Use it on your own risk.

Licence:
  BSD

Usage:
  Add one or all these line to the pppoe-server configuration (or option file).
  I think that you will need at least external_pap_auth_hook

  external_pap_check_hook /path/of/pap_check_hook
  external_pap_passwd_hook /path/of/pap_passwd_hook
  external_pap_auth_hook /path/of/pap_auth_hook
  external_allowed_address_hook /path/of/allowed_address_hook
  external_ip_choose_hook /path/of/ip_choose_hook
  external_ip_up_notifier /path/of/ip_up_notifier
  external_ip_down_notifier /path/of/ip_down_notifier

  // if you need to see what's going on, enable verbose
  external_verbose 1

*/

//standard
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>

//address
#include <arpa/inet.h>

//stat
#include <sys/stat.h>
#include <sys/types.h>

//internal ppp
#include "pppd.h"

#define USERNAME_MAX_LEN 20

char pppd_version[] = VERSION;

static char *external_pap_check_hook = NULL;
static char *external_pap_passwd_hook = NULL;
static char *external_pap_auth_hook = NULL;
static char *external_ip_choose_hook = NULL;
static char *external_allowed_address_hook = NULL;
static char *external_ip_up_notifier = NULL;
static char *external_ip_down_notifier = NULL;

static int *external_verbose = 0;

struct externalcmd_state {
    char ext_ip_addr[INET_ADDRSTRLEN];
    char ext_username[USERNAME_MAX_LEN];
};

static struct externalcmd_state ext_state;
    
static int PROGRAM_BINARY_RETURN = 3; // 0|1|-1

static option_t options[] = {
    { "external_pap_check_hook", o_string, &external_pap_check_hook},
    { "external_pap_passwd_hook", o_string, &external_pap_passwd_hook},
    { "external_pap_auth_hook", o_string, &external_pap_auth_hook},
    { "external_ip_choose_hook", o_string, &external_ip_choose_hook },
    { "external_allowed_address_hook", o_string, &external_allowed_address_hook },
    { "external_ip_up_notifier", o_string, &external_ip_up_notifier },
    { "external_ip_down_notifier", o_string, &external_ip_down_notifier },
    
    { "external_verbose", o_int, &external_verbose },
    { NULL }
};

/*
Debug. Don't use
*/
void _info_log(char *msg) {
    FILE *file;
    file = fopen("/tmp/pppoe_data.log","a+");
    fprintf(file,"(log) %s\n", msg);
    fflush(file);
    fclose(file);
}

/*
format string before call the real [info|error|fatal]
*/
static char *_info (char *msg, char *cmd_prefix, char *arg) {
    
    char *msgarg = malloc(strlen(msg) + strlen(arg));
    sprintf(msgarg, msg, arg);
    
    char *msgtot = malloc(strlen(msgarg) + strlen(cmd_prefix)+3); // add ", "
    
    strcpy(msgtot, cmd_prefix);
    strcat(msgtot, ", ");
    strcat(msgtot, msgarg);
    
    free(msgarg);
    
    //_info_log(msgtot);
    
    return msgtot;
}

/*
Control if the program configuration string are present
*/
static int _program_present(char *cmd_prefix, char *program_path){
    if (! program_path) {
        char *msg = _info("No command present.", cmd_prefix, "");
        error(msg);
        free(msg);
        return 1;
    }
    return 0;
}

/*
Common function for call external programs and handle its behaivour
*/
static int _call_ext_program (char *cmd_prefix, char *program_path, char *in_buffer, int len_buff_read) {
     
    FILE *fp;
    int status, program_path_found=1;
    char *s_end_clean;
    struct stat r_path;
    
    // control if parameter is present
    if (_program_present(cmd_prefix, program_path))
        return 1;
    
    // debug the command
    if (external_verbose) {
        char *msg = _info("Program path: %s", cmd_prefix, program_path);
        info(msg);
        free(msg);
    }
        
    // if the command have also parameters inside the path, control the command part
    if (strstr(program_path, " ")) {
         char new_program_path[strlen(program_path)];
         strcpy((char *)new_program_path, program_path);
         strtok(new_program_path," ");
         if (stat(new_program_path, &r_path) == -1)
            program_path_found=0;
    }
    else {
        if (stat(program_path, &r_path) == -1) 
            program_path_found=0;
        }
    
    if (! program_path_found) {
        //control if the program exists
        char *msg = _info("Program path not found: %s", cmd_prefix, program_path);
        fatal(msg);
        free(msg);
        return 2;
    }
    
    
    fp = popen(program_path, "r");
    if (fp == NULL) {
        // Handle error
        char *msg = _info("Error opening communication with: %s", cmd_prefix, program_path);
        fatal(msg);
        free(msg);
        return 3;
    }
    
    
    if (fgets(in_buffer, len_buff_read, fp) == NULL) {
        char *msg = _info("Error reading output: %s", cmd_prefix, program_path);
        error(msg);
        free(msg);
        return 4;
    }
    
    status = pclose(fp);
    if (status == -1) {
        /* Error reported by pclose() */
        char *msg = _info("Error on pclose: %s", cmd_prefix, program_path);
        error(msg);
        free(msg);
        return 5;
    }
    
    //we have the data, debug if need
    if (external_verbose) {
        char *msg = _info("Message returned: %s", cmd_prefix, (char *)in_buffer);
        info(msg);
        free(msg);
    }
    
    

    //clean the non white-spaces chars
    
    // Trim leading space
    while(isspace(*in_buffer)) in_buffer++;
    
    // Trim trailing space
    s_end_clean = in_buffer + strlen(in_buffer) - 1;
    while(s_end_clean > in_buffer && isspace(*s_end_clean)) s_end_clean--;
    
    // Write new null terminator
    *(s_end_clean+1) = 0;

    return 0;
    
}

/*
The pap_check_hook is called to check whether there is any possibility
that the peer could authenticate itself to us.
@return:
  1, pppd will ask the peer to authenticate itself.
  0, pppd will not ask the peer to authenticate itself 
*/
static int f_ext_pap_check_hook(void) {
    
    char c_pap_check_hook[PROGRAM_BINARY_RETURN];
    if (_call_ext_program("external_pap_check_hook", external_pap_check_hook, c_pap_check_hook, PROGRAM_BINARY_RETURN))
        // don't worry about errors and leave the user try to auth
        return 1;
    
    return atoi(c_pap_check_hook);
}

/*
It's called to determine what username and password should use in authenticating 
itself to the peer with PAP.  The user string will already be initialized, by the 
user, name or from the hostname, but can be changed if necessary.
@return:
  0, pppd will use the values at *user and *passwd
  -1, pppd will look in the pap-secrets file, or use the value from the +ua or 
      password option, as it would normally.
*/
static int f_ext_pap_passwd_hook(char *user, char *passwd) {
    
    char c_pap_passwd_hook[PROGRAM_BINARY_RETURN];
    
    if (_call_ext_program("external_pap_passwd_hook", external_pap_passwd_hook, c_pap_passwd_hook, PROGRAM_BINARY_RETURN))
        return -1;
    
    return atoi((char*)c_pap_passwd_hook);
}

/*
called for determine whether the username and password supplied 
by the peer are valid.
@return:
  1 if valid 
  0 if not
*/
static int f_ext_pap_auth_hook(char *user, char *passwd,
        char **msgp,
        struct wordlist **paddrs,
        struct wordlist **popts) {
        
    char c_pap_auth_hook[PROGRAM_BINARY_RETURN];
    
    if (_program_present("external_pap_auth_hook", external_pap_auth_hook))
        return 0;
        
    char *complete_program = malloc(strlen(external_pap_auth_hook) + strlen(user) + strlen(passwd) + 3);
    strcpy(complete_program, external_pap_auth_hook);
    strcat(complete_program, " ");
    strcat(complete_program, user);
    strcat(complete_program, " ");
    strcat(complete_program, passwd);
    
    if (_call_ext_program("external_pap_auth_hook", complete_program, c_pap_auth_hook, PROGRAM_BINARY_RETURN))
        return 0;
    
    free(complete_program);
    
    int ext_ret = atoi((char*)c_pap_auth_hook);
    if (ext_ret)
            strcpy(ext_state.ext_username, user);
    
    return ext_ret;

}


/*
Called to see if a peer is allowed to use the specified address.
@return:
  1 the address is accepted
  0 the address is rejected.  
  -1 the address is verified in the normal away against the appropriate options and secrets files.
*/
static int f_ext_allowed_address_hook (u_int32_t addr) 
{
    char ad[INET_ADDRSTRLEN]; 
    inet_ntop(AF_INET, &addr, ad, INET_ADDRSTRLEN);

    char c_allowed_address_hook[PROGRAM_BINARY_RETURN];

    if (_program_present("external_allowed_address_hook", external_allowed_address_hook))
        return 1;
        
    char *complete_program = malloc(strlen(external_allowed_address_hook) + INET_ADDRSTRLEN + 1); //1 for space
    strcpy(complete_program, external_allowed_address_hook);
    strcat(complete_program, " ");
    strcat(complete_program, ad);

    
    if (_call_ext_program("external_allowed_address_hook", 
                    complete_program, c_allowed_address_hook, PROGRAM_BINARY_RETURN))
        return -1;


    return atoi((char*)c_allowed_address_hook);
}

/*
It gives a plugin the opportunity to set the IP address for the peer; the address
should be stored in *addrp.
*/
static void f_ext_ip_choose_hook(u_int32_t *addrp) {

    char ip_choose_hook[INET_ADDRSTRLEN];
    
    if (_program_present("external_ip_choose_hook", external_ip_choose_hook))
        return;
        
    char *complete_program = malloc(strlen(external_ip_choose_hook) + strlen(ext_state.ext_username) + 1); //1 for space
    strcpy(complete_program, external_ip_choose_hook);
    strcat(complete_program, " ");
    strcat(complete_program, ext_state.ext_username);
    
    if (_call_ext_program("external_ip_choose_hook", complete_program, ip_choose_hook, INET_ADDRSTRLEN))
        return;
    
    if (! inet_pton(AF_INET, ip_choose_hook, addrp) ) 
        error("f_ext_ip_choose_hook. Error on ip conversion");
    
    //save the ip into internal struct
    strcpy(ext_state.ext_ip_addr, ip_choose_hook);
    
    return;
}


/*
ip_up_notifier.  This is called when IPCP has come up.
*/
static void f_ext_ip_up_notifier(void *opaque, int arg) {
    
    char c_ip_up_notifier[PROGRAM_BINARY_RETURN];

    if (_program_present("external_ip_up_notifier", external_ip_up_notifier))
        return;
        
    char *complete_program = malloc(strlen(external_ip_up_notifier) + strlen(ext_state.ext_username) + 1); //1 for space
    strcpy(complete_program, external_ip_up_notifier);
    strcat(complete_program, " ");
    strcat(complete_program, ext_state.ext_username);

    _call_ext_program("external_ip_up_notifier", complete_program, c_ip_up_notifier, PROGRAM_BINARY_RETURN);
}

/*
ip_down_notifier.  This is called when IPCP goes down.
*/
static void f_ext_ip_down_notifier(void *opaque, int arg) {
    
    char c_ip_down_notifier[PROGRAM_BINARY_RETURN];

    if (_program_present("external_ip_down_notifier", external_ip_down_notifier))
        return;
        
    char *complete_program = malloc(strlen(external_ip_down_notifier) + strlen(ext_state.ext_username) + 1); //1 for space
    strcpy(complete_program, external_ip_down_notifier);
    strcat(complete_program, " ");
    strcat(complete_program, ext_state.ext_username);
   
    _call_ext_program("external_ip_down_notifier", complete_program, c_ip_down_notifier, PROGRAM_BINARY_RETURN);
}

void plugin_init (void) {
    add_options (options);

    //pap
    pap_check_hook = f_ext_pap_check_hook;
    pap_passwd_hook = f_ext_pap_passwd_hook;
    pap_auth_hook = f_ext_pap_auth_hook;

    //chap
    //TODO

    //ip address
    allowed_address_hook = f_ext_allowed_address_hook;
    ip_choose_hook = f_ext_ip_choose_hook;
    
    //notifiers
    
    add_notifier(&ip_up_notifier, f_ext_ip_up_notifier, NULL);
    add_notifier(&ip_down_notifier, f_ext_ip_down_notifier, NULL);
    
}
